package com.saville.mcmaster;

/*
 * Copyright 2008 Wink Saville
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.HashMap;
import java.util.concurrent.atomic.*;
import java.net.*;

import com.saville.debug.*;
import com.saville.msgcomp.*;
import com.saville.mc.*;

public class McMaster extends ActiveMc {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Log.print("Hello McMaster main E");
        new McMaster();
        Log.print("Hello McMaster main X");
    }

    McMaster() {
        super("McMaster");
        df = Log.VERBOSE;

        TcpMsgPipeConnectionServer tmpcs = new TcpMsgPipeConnectionServer(McConst.MCMASTER_PORT,
                this);

        if (!tmpcs.start()) {
            throw new RuntimeException("Could not start connection server");
        }
    }

    @Override
    public void processMsg(Msg pipeMsg) {
        //Log.d(df, "McMaster: processMsg() E pipeMsg guid=0x%x cmd=0x%x", pipeMsg.guid, pipeMsg.cmd);

        if (pipeMsg.is(McConst.MC_GUID, McConst.MC_CMD_STARTED)) {
            Log.v(df, "McMastger: MC_CMD_STARTED");
        } else if (pipeMsg.is(McConst.MCPIPE_GUID, McConst.MCPIPE_CMD_CONNECTED)) {
            /*
             * Create a new pipe for this socket
             */
            Socket socket = (Socket) pipeMsg.obj;
            int dstSubId = mDstSubId++;
            Log.d(df, "McMaster: processMsg() connected dstSubId=0x%x", dstSubId);

            /* TODO: When the pipe closes we need to remove from mapping */
            connMapping.put(dstSubId, new TcpMsgPipe(socket, this.mMcId.id, dstSubId));

        } else if (pipeMsg.is(McConst.MCPIPE_GUID, McConst.MCPIPE_CMD_CLOSED)) {
            Log.d(df, "McMaster: processMsg() remove dstSubId=0x%x", pipeMsg.dstSubId);
            connMapping.remove(pipeMsg.dstSubId);
        } else if (pipeMsg.is(McConst.MCPIPE_GUID, McConst.MCPIPE_CMD_PRODUCER_MSG)) {
            Msg msg = (Msg) pipeMsg.obj;
            TcpMsgPipe pipe = connMapping.get(pipeMsg.dstSubId);


            if (msg.is(McConst.MCMASTER_GUID, McConst.MCMASTER_CMD_REGISTER_DEV)) {
                Log.d(df, "McMaster: processMsg() RegisterDev");
                msg.obj = registerDevId((DevId) msg.obj);
                Msg replyMsg = mMcMgr.getReplyMsg(msg, McConst.STATUS_OK);
                pipe.queueMsg(replyMsg);
            } else if (msg.is(McConst.MCMASTER_GUID, McConst.MCMASTER_CMD_LOOKUP_DEV)) {
                Log.d(df, "McMaster: processMsg() LookupDev");
                Msg replyMsg;
                
                DevId devId = (DevId) msg.obj;
                DevId lookupResult = lookupDevId(devId.urn);
                if (lookupResult != null) {
                    replyMsg = mMcMgr.getReplyMsg(msg, McConst.STATUS_OK);
                    replyMsg.obj = lookupResult;
                } else {
                    replyMsg = mMcMgr.getReplyMsg(msg, McConst.STATUS_ERR);
                }
                pipe.queueMsg(replyMsg);
            } else if (msg.is(McConst.MCMASTER_GUID, McConst.MCMASTER_CMD_FORWARD_MSG)) {
                Log.d(df, "McMaster: processMsg() ForwardMsg");
                
            } else if (msg.is(McConst.MC_GUID, McConst.MC_CMD_ECHO)) {
                Log.d(df, "McMaster: processMsg() Echo");
                pipe.queueMsg(getReplyMsg(msg, McConst.STATUS_ECHO));
            } else {
                Log.e(df, "McMaster: received unknown msg guid=0x%x cmd=0x%x", msg.guid, msg.cmd);
                if (msg.cmdIsRequest()) {
                    pipe.queueMsg(getReplyMsg(msg, McConst.STATUS_UNKNOWN_CMD));
                }
            }
        } else {
            Log.e(df, "McMaster: received unknown pipeMsg guid=0x%x cmd=0x%x", pipeMsg.guid,
                pipeMsg.cmd);
            if (pipeMsg.cmdIsRequest()) {
                sendReplyMsg(pipeMsg, McConst.STATUS_UNKNOWN_CMD);
            }
        }

        //Log.d(df, "McMaster: processMsg() X");
    }

    /*
     * TODO: registerDevId and lookupDevId are "identical" to McMgr.registerMcId
     * and lookupId maybe these should be generics as to only real difference is
     * the types.
     */

    public DevId registerDevId(DevId devId) {
        DevId lookupResult;

        lookupResult = lookupDevId(devId.urn);
        if (lookupResult == null) {
            devId.id = mNextId.getAndIncrement();
            if (devId.id == -1) {
                throw new RuntimeException(mName + ": devId.id overflowed, DIE");
            }
    
            /*
             * Update the databases
             */
            devUrnMapping.put(devId.urn, devId);
            devIdMapping.put(devId.id, devId);
        } else {
            /* Update friendly name in case it changed */
            lookupResult.friendlyName = devId.friendlyName;
            devId = lookupResult;
        }

        Log.v(df, "%s: registerDevId McId devId %s", mName, devId);
        return devId;
    }

    /*
     * Lookup the DevId associated with urn.
     * 
     * @return null if not found
     */
    public DevId lookupDevId(String urn) {
        DevId devId;

        try {
            devId = devUrnMapping.get(urn);
        } catch (Exception e) {
            devId = null;
        }

        Log.v(df, "%s: lookupDevId urn=%s id=%d", mName, urn, devId == null ? -1 : devId.id);
        return devId;
    }

    /*
     * Mapping of Name to devId
     */
    private HashMap<String, DevId> devUrnMapping = new HashMap<String, DevId>();

    /*
     * Map of unique id to McId
     */
    private HashMap<Long, DevId> devIdMapping = new HashMap<Long, DevId>();

    /*
     * Map connection id to pipe
     */
    private HashMap<Integer, TcpMsgPipe> connMapping = new HashMap<Integer, TcpMsgPipe>();

    /*
     * Integer with the next DevId.id
     */
    private AtomicInteger mNextId = new AtomicInteger();

    /*
     * Sub destination id
     */
    private int mDstSubId;
}
